package debtbundler.domain.entity.impl;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import debtbundler.domain.bll.Utils;
import debtbundler.domain.entity.MoneyPool;
import debtbundler.domain.entity.Transaction;

/**
 * Base class for money pools. 
 * 
 * @author Anssi
 *
 */
abstract class AbstractMoneyPoolImpl extends AbstractListenableEntity implements MoneyPool {
	private static final long serialVersionUID = 1L;
	private static Logger logger = Logger.getLogger(AbstractMoneyPoolImpl.class.getName());
	
	protected static long poolIdSeq = 1;
	
	protected String name;
	protected HashSet<Transaction> transactions = new HashSet<Transaction>();
	
	@Override
	public String getName() {
		return name;
	}

	/**
	 * @param name	name of this pool
	 */
	public void setName(String name) {
		if (name == null) {
			throw new IllegalArgumentException("Null name not allowed");
		}
		this.name = name.trim();
		logger.log(Level.FINE, "Name of pool now {0}", this);
		fireEntityUpdateEvent();
	}
	
	@Override
	public String toString() {
		return name;
	}	

	@Override
	public Set<Transaction> getTransactions() {
		return Collections.unmodifiableSet(this.transactions);
	}
	
	@Override
	public Set<Transaction> getInputTransactions() {
		HashSet<Transaction> inputs = new HashSet<Transaction>();
		for (Transaction tr : this.transactions) {
			if ( this.equals(tr.getToPool()) ) {
				inputs.add(tr);
			}
		}
		return Collections.unmodifiableSet(inputs);
	}	

	@Override
	public Set<Transaction> getOutputTransactions() {
		HashSet<Transaction> outputs = new HashSet<Transaction>();
		for (Transaction tr : this.transactions) {
			if ( this.equals(tr.getFromPool()) ) {
				outputs.add(tr);
			}
		}
		return Collections.unmodifiableSet(outputs);
	}
	
	/**
	 * Adds a transaction to this sharedCost. The ends of the transaction must be 
	 * instances of <code>AbstractMoneyPoolImpl</code>.
	 * 
	 * @param transaction	the transaction to add
	 * @return				<code>true</code> if the transaction was added
	 */
	protected boolean addTransaction(Transaction transaction) {
		if (transaction == null) {
			throw new IllegalArgumentException("Not allowed to add null transaction");
		}
		if (!this.equals(transaction.getFromPool()) && !this.equals(transaction.getToPool()) ) {
			throw new IllegalArgumentException("Not allowed to add an unrelated transaction");
		}
		if (transaction.getFromPool() == null || transaction.getToPool() == null) {
			throw new IllegalArgumentException("Not allowed to add transaction with a null end");
		}
		
		AbstractTransactionImpl tr = (AbstractTransactionImpl)transaction;
		boolean added = this.transactions.add(tr);
		if (added) {
			if ( this.equals(tr.fromPool) )
				tr.toPool.addTransaction(transaction);
			else
				tr.fromPool.addTransaction(transaction);
			logger.log(Level.FINE, "Added transaction {0} to pool {1}", new Object[]{transaction, this});
			fireEntityUpdateEvent();
		}
		return added;
	}
	
	@Override
	public boolean removeTransaction(Transaction transaction) {
		boolean removed = this.transactions.remove(transaction);
		if (removed) {
			if ( this.equals(transaction.getFromPool()) )
				transaction.getToPool().removeTransaction(transaction);
			else 
				transaction.getFromPool().removeTransaction(transaction);
			logger.log(Level.FINE, "Removed transaction {0} from pool {1}", new Object[]{transaction, this});
			fireEntityUpdateEvent();
		}
		return removed;
	}	

	@Override
	public double getBalance() {
		return Utils.sum(getInputTransactions()) - Utils.sum(getOutputTransactions());
	}

	@Override
	public boolean isBalanced() {
		return Math.abs(getBalance()) <= 0.005;
	}



}
